from fastapi import HTTPException
from starlette import status

from application.aiohttp_utils import put_request_with_body, get_request_util, delete_request_with_path
from application.utils import get_rich_logger, http_option
from db.metastore import connect_with_pocketbase, user_auth_via_client, client_create_new, client_fetch_from, \
    client_list_from, client_search_first_item, client_delete_record, client_fetch_full_list_from
from application.handle_exception import pb_operation_exception
from domain.authentication import User
from domain.deployment import DeploymentSchemaInDB, DeploymentSchema

logger = get_rich_logger()


async def deploy_new_serving_app(
        cluster: str,
        deployment_conf: DeploymentSchema,
        user: User
):
    # add application metadata
    deployment_conf_in_db = DeploymentSchemaInDB(
        name=deployment_conf.name,
        uuid=user.uuid.hex,
        cluster=cluster,
        app_definition=deployment_conf.json()
    )

    with connect_with_pocketbase() as db_client:
        _ = user_auth_via_client(
            client=db_client,
            user=user
        )
        create_response = await client_create_new(
            client=db_client,
            collection="serving",
            data=deployment_conf_in_db.dict()
        )
        if not create_response:
            raise pb_operation_exception

    # deploy with REST API
    get_applications_rsp = await get_request_util(
        request_url=f"{cluster}/api/serve/applications/"
    )
    current_apps: dict = get_applications_rsp['response']['applications']
    final_deployment = deployment_conf.dict()
    final_deployment['name'] = f"{deployment_conf.name}-{create_response.id}"
    if not current_apps:
        logger.debug("No apps are running.")
        req_body = {
            "http_options": http_option,
            "applications": [final_deployment]
        }
    else:
        logger.debug(f"Current running apps info: {current_apps}")
        current_apps[final_deployment['name']] = final_deployment
        req_body = {
            "http_options": http_option,
            "applications": current_apps
        }
    deploy_ret = await put_request_with_body(
        request_url=f"{cluster}/api/serve/applications/",
        request_body=req_body
    )
    if deploy_ret['status'] != 200:
        raise HTTPException(
            status_code=status.HTTP_304_NOT_MODIFIED,
            detail="Deployment failed. Please check your deployment config and cluster endpoint."
        )
    logger.debug("Done application deployment.")

    return create_response


async def fetch_cluster_applications_info(
        cluster: str,
        record_id: str | None
):
    with connect_with_pocketbase() as db_client:
        if cluster is None:
            record_response = await client_fetch_full_list_from(
                client=db_client,
                collection="serving"
            )
        else:
            record_response = await client_fetch_from(
                client=db_client,
                collection="serving",
                target_id=record_id
            )

    return record_response


async def tear_down_serving_app_and_delete_record(
        cluster: str,
        application_name: str,
        user: User
):
    # delete metadata record
    with connect_with_pocketbase() as db_client:
        search_response = await client_search_first_item(
            client=db_client,
            collection="serving",
            filter_predicate=f"uuid = \'{user.uuid.hex}\' "
                             f"&& cluster = \'{cluster}\' "
                             f"&& name = \'{application_name.split('-')[0]}\'"
        )
        if search_response:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="No such application."
            )
        delete_response = await client_delete_record(
            client=db_client,
            collection="serving",
            target_id=application_name.split('-')[1]
        )
        if not delete_response:
            raise pb_operation_exception

    # re-deploy with REST API
    get_applications_rsp = await get_request_util(
        request_url=f"{cluster}/api/serve/applications/"
    )
    current_apps: dict = get_applications_rsp['response']['applications']
    if not current_apps:
        logger.debug("No application is running")
        raise HTTPException(
            status_code=status.HTTP_304_NOT_MODIFIED,
            detail="No application is running."
        )
    updated_apps = [(key, value) for key, value in current_apps.items() if key != application_name]
    req_body = {
        "http_options": http_option,
        "applications": updated_apps
    }
    deploy_ret = await put_request_with_body(
        request_url=f"{cluster}/api/serve/applications/",
        request_body=req_body
    )
    if deploy_ret['status'] != 200:
        raise HTTPException(
            status_code=status.HTTP_304_NOT_MODIFIED,
            detail="Re-Deployment failed."
        )
    logger.debug("Done application Re-deployment.")


async def shutdown_all_application(
        cluster: str,
        user: User
):
    shutdown_ret = await delete_request_with_path(
        request_url=f"{cluster}/api/serve/applications/"
    )
    if shutdown_ret['status'] != 200:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Shutdown execution failed."
        )

    # delete metadata record
    with connect_with_pocketbase() as db_client:
        search_response = await client_list_from(
            client=db_client,
            collection="serving",
            filter_predicate=f"uuid = \'{user.uuid.hex}\' && cluster = \'{cluster}\'"
        )
        if search_response:
            raise HTTPException(
                status_code=status.HTTP_304_NOT_MODIFIED,
                detail=f"No more application records of the cluster: {cluster}"
            )
        for item in search_response.items:
            app_record_id = item.id
            delete_response = await client_delete_record(
                client=db_client,
                collection="serving",
                target_id=app_record_id
            )
            if not delete_response:
                raise pb_operation_exception

    return True
